﻿Imports System
Imports System.Drawing
Imports System.Windows.Forms
Imports System.ComponentModel
Imports System.Math
''' <summary>
''' 源代码编辑器的文字表示区域
''' </summary>
''' <remarks>
''' 一定要继承于Control，不能继承语UserControl。UserControl是一个容器
''' </remarks>
Partial Public Class TextBoxEx
#Region "事件"
    ''' <summary>
    ''' 横向滚动事件
    ''' </summary>
    ''' <remarks></remarks>
    Public Event ChangeHScroll()
    ''' <summary>
    ''' 纵向滚动事件
    ''' </summary>
    ''' <remarks></remarks>
    Public Event ChangeVScroll()
    ''' <summary>
    ''' 插入点变更事件
    ''' </summary>
    ''' <remarks></remarks>
    Public Event InputPositionChanged()
    ''' <summary>
    ''' 插入/更新模式变更事件
    ''' </summary>
    ''' <param name="_InputMode"></param>
    ''' <remarks></remarks>
    Public Event InputModeChanged(ByVal _InputMode As EnumInputMode)
    ''' <summary>
    ''' 开始列模式事件
    ''' </summary>
    ''' <param name="LineNumber"></param>
    ''' <remarks></remarks>
    Public Event ColumnModeStart(ByVal LineNumber As Integer)
    ''' <summary>
    ''' 结束列模式事件
    ''' </summary>
    ''' <param name="LineNumber"></param>
    ''' <remarks></remarks>
    Public Event ColumnModeEnd(ByVal LineNumber As Integer)
    ''' <summary>
    ''' 清除列模式事件
    ''' </summary>
    ''' <remarks></remarks>
    Public Event ClearColumn()
    ''' <summary>
    ''' 文件拖曳事件
    ''' </summary>
    ''' <param name="FirstFileName"></param>
    ''' <remarks></remarks>
    Public Event DropFile(ByVal FirstFileName As String)
    ''' <summary>
    ''' 请求重新做折行显示
    ''' </summary>
    ''' <remarks></remarks>
    Public Event ReWordWrap()
    ''' <summary>
    ''' 没有发现关键字
    ''' </summary>
    ''' <remarks></remarks>
    Public Event WordNotFound()
    ''' <summary>
    ''' 
    ''' </summary>
    ''' <remarks></remarks>
    Public Event FontChange(ByVal NewFont As Font)
#End Region
#Region "属性"
    Dim mCodeFont As Font = Me.Font
    Friend Sub SetCodeFont(ByVal value As Font)
        mCodeFont = value
        offsetUnit = MeasureTextWidth(Space(10))
        VisibleLines = Math.Floor(Me.Size.Height / ExFont.Height)
        mCaret.ChangeHeight(ExFont.Height)
        Me.Refresh()
    End Sub
    ''' <summary>
    ''' 高亮关键字
    ''' </summary>
    ''' <remarks></remarks>
    Private strKeyWord As String = String.Empty
    <Description("高亮关键字"), Category("源代码")> _
    Property HeightLightWord() As String
        Get
            Return strKeyWord
        End Get
        Set(ByVal value As String)
            strKeyWord = value
            ExDocument.SetHeightLightWord(strKeyWord)
            Me.Refresh()
        End Set
    End Property
    ''' <summary>
    ''' 检索关键字
    ''' </summary>
    ''' <remarks></remarks>
    Private strSearchWord As String = String.Empty
    <Description("检索关键字"), Category("源代码")> _
    Property SearchWord() As String
        Get
            Return strSearchWord
        End Get
        Set(ByVal value As String)
            If value = strSearchWord Then Exit Property
            strSearchWord = value
            ExDocument.SetSerachKey(strSearchWord)
            Me.Refresh()
        End Set
    End Property
#End Region
#Region "私有变量"
    ''' <summary>
    ''' 工具提示
    ''' </summary>
    ''' <remarks></remarks>
    Private WithEvents mToolTip As New ToolTip
    ''' <summary>
    ''' 智能完成
    ''' </summary>
    ''' <remarks></remarks>
    Private WithEvents IntellisenseBox As New ListBoxEx
    ''' <summary>
    ''' 设定IME输入法窗口的位置
    ''' </summary>
    ''' <remarks></remarks>
    Private mIME As Ime
    ''' <summary>
    ''' 光标类
    ''' </summary>
    ''' <remarks></remarks>
    Private mCaret As Caret
    ''' <summary>
    ''' 光标在画面上的位置
    ''' </summary>
    ''' <remarks></remarks>
    Private CaretPosition As Point
    ''' <summary>
    ''' 鼠标底下的文字
    ''' </summary>
    ''' <remarks></remarks>
    Private CurrentMouseWord As SingleWord
    ''' <summary>
    ''' 插入/更新 输入模式
    ''' </summary>
    ''' <remarks></remarks>
    Public mInputMode As EnumInputMode = EnumInputMode.InsertChar
    ''' <summary>
    ''' IsDrawString如果为假的话，不将字符绘制到窗体
    ''' </summary>
    ''' <remarks></remarks>
    Private IsDrawString As Boolean
    ''' <summary>
    ''' 这个内容是否从IME传递过来
    ''' </summary>
    ''' <remarks></remarks>
    Private CharFormIME As Boolean
#End Region
#Region "内部方法"
    Sub New()
        ' 此调用是 Windows 窗体设计器所必需的。
        InitializeComponent()
        ' 在 InitializeComponent() 调用之后添加任何初始化。
        '必须把控件设置为可以获得焦点的，不然没有办法实现GotFocus事件
        SetStyle(ControlStyles.Selectable, True)
        '改变大小的时候，自动重画
        SetStyle(ControlStyles.ResizeRedraw, True)
        '改善绘图质量
        SetStyle(ControlStyles.Opaque, True)
        Me.DoubleBuffered = True
        '初始位置为第一行，第一列，第一个可见行的行号为1
        CaretPosition = New Point(0, 0)
        '文档插入点
        ExDocument.InputPosition = New Point(1, 1)
        '这行显示宽度
        ExDocument.WordWrapWidth = Me.Width
        '第一个可见行
        FirstVisiableLineNumber = 1
        'IME窗口
        Me.mIME = New Ime(Me.Handle, Me.Font)
        '插入光标
        mCaret = New Caret(Me.Handle, Me.Font.Height, Me.mIME)
        '光标设定为输入样式
        Me.Cursor = System.Windows.Forms.Cursors.IBeam
        'Tooltip的初始化
        Me.mToolTip.SetToolTip(Me, String.Empty)
        '自动完成
        IntellisenseBox.Visible = False
        Me.Controls.Add(IntellisenseBox)
        AddHandler IntellisenseBox.GetCompleteWord, AddressOf CompleteWord
    End Sub
    ''' <summary>
    ''' 重置所有变量
    ''' </summary>
    ''' <remarks></remarks>
    Sub ResetAll()
        offset = 0
        mInputMode = EnumInputMode.InsertChar
        MouseEff = MouseMoveEffect.None
        FirstVisiableLineNumber = 1
        FirstLineChanged()
        Me.mCaret.DestroyCaret()
        Me.mCaret.CreateCaret()
        Me.CaretPosition = New Point(0, 0)
        Me.mCaret.ShowCaret()
        ExDocument.InputPosition = New Point(1, 1)
        ExDocument.StepList.Clear()
        ExDocument.SelectStartRealPos = New Point(0, 0)
        ExDocument.SelectEndRealPos = New Point(0, 0)
        HeightLightWord = String.Empty
        SearchWord = String.Empty
    End Sub
    ''' <summary>
    ''' 捕获系统消息
    ''' </summary>
    ''' <param name="m">系统消息</param>
    ''' <remarks></remarks>
    Protected Overrides Sub WndProc(ByRef m As Message)
        Select Case m.Msg
            '所有需要画的字符都来自语WM_CHAR，中文，日语的话，还回发生WM_IME_CHAR，造成KeyPress里2次同样的事件
            '基于这个原因，我们吧WM_IME_CHAR产生的KeyPress过滤掉
            Case WM_IME_CHAR
                CharFormIME = True
            Case WM_CHAR
                CharFormIME = False
        End Select
        MyBase.WndProc(m)
    End Sub
    ''' <summary>
    ''' 获得焦点
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub TextBoxEx_GotFocus(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.GotFocus
        '自定义文本编辑控件中的光标控制
        '如果当前光标行在表示范围内的话，则表示当前光标
        mCaret.CreateCaret()
        mCaret.SetCaretPos(CaretPosition)
        mCaret.ShowCaret()
        If Not (ExDocument.InputPosition.Y >= FirstVisiableLineNumber And _
           ExDocument.InputPosition.Y <= Math.Min(FirstVisiableLineNumber + VisibleLines - 1, ExDocument.MaxDisplayLineNumber)) Then
            mCaret.HideCaret()
        End If
    End Sub
    ''' <summary>
    ''' 失去焦点
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub TextBoxEx_LostFocus(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.LostFocus
        '隐藏和销毁光标
        mCaret.HideCaret()
        mCaret.DestroyCaret()
    End Sub
    ''' <summary>
    ''' 尺寸变化结束时触发（替换原来的实时变化的Resize事件）
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub TextBoxEx_SizeChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.SizeChanged
        If (ExFont Is Nothing) Or (Me.Size = New Size(0, 0)) Then
            '最小化的时候也退出
            Exit Sub
        End If
        VisibleLines = Math.Floor(Me.Size.Height / ExFont.Height)
        If ExWordWrapMode And Me.Width <> ExDocument.WordWrapWidth Then
            ExDocument.UnDoWrapWord()
            ExDocument.DoWrapWord(Me.Width)
        Else
            ExDocument.ResetMaxLineWidth()
        End If
        ExDocument.WordWrapWidth = Me.Width
        RaiseEvent ChangeHScroll()
    End Sub
    ''' <summary>
    ''' 首行变更
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub FirstLineChanged()
        '如果当前行在表示范围以内
        If ExDocument.DisplayPosition.Y >= FirstVisiableLineNumber And _
           ExDocument.DisplayPosition.Y <= Math.Min(CInt(FirstVisiableLineNumber + VisibleLines - 1), CInt(ExDocument.MaxDisplayLineNumber)) Then
            '调整光标的位置
            CaretPosition.Y = ExFont.Height * (ExDocument.DisplayPosition.Y - FirstVisiableLineNumber)
            mCaret.SetCaretPos(CaretPosition)
            Debug.WriteLine("Show FirstLineChanged")
            mCaret.ShowCaret()
        Else
            Debug.Print("Hide FirstLineChanged")
            mCaret.HideCaret()
        End If
        Me.Refresh()
        RaiseEvent ChangeVScroll()
    End Sub
    ''' <summary>
    ''' 通过文档的插入点，计算文档的光标信息
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub ResetCaretPos()
        CaretPosition.X = MeasureTextWidth(Mid(ExDocument.CurrentLineText, 1, ExDocument.DisplayPosition.X - 1)) - offset
        CaretPosition.Y = ExFont.Height * (ExDocument.DisplayPosition.Y - FirstVisiableLineNumber)
    End Sub
    ''' <summary>
    ''' 通过改变滚动条的值，将画面移动到插入点位置
    ''' </summary>
    ''' <remarks></remarks>
    Sub MakeCaretVisible()
        If CaretPosition.X = 0 Then
            If (offset <> 0) And ExDocument.DisplayPosition.X = 1 Then
                offset = 0
            End If
        End If
        '调整光标位置
        If CaretPosition.X < 0 Then
            offset -= Ceiling(Abs(CaretPosition.X) / offsetUnit) * offsetUnit
            CaretPosition.X = +Ceiling(Abs(CaretPosition.X) / offsetUnit) * offsetUnit
        End If
        If CaretPosition.X > Me.Width Then
            offset += Ceiling((CaretPosition.X - Me.Width) / offsetUnit) * offsetUnit
            CaretPosition.X -= Ceiling((CaretPosition.X - Me.Width) / offsetUnit) * offsetUnit
        End If
        If ExDocument.DisplayPosition.Y < FirstVisiableLineNumber Then
            FirstVisiableLineNumber = ExDocument.DisplayPosition.Y
            FirstLineChanged()
        End If
        If ExDocument.DisplayPosition.Y > FirstVisiableLineNumber + VisibleLines - 1 Then
            FirstVisiableLineNumber = ExDocument.DisplayPosition.Y - VisibleLines + 1
            FirstLineChanged()
        End If
        '如果不是折行显示模式
        If Not ExWordWrapMode Then RaiseEvent ChangeHScroll()
    End Sub
    ''' <summary>
    ''' 完成智能完成
    ''' </summary>
    ''' <param name="strWord"></param>
    ''' <remarks></remarks>
    Private Sub CompleteWord(ByVal strWord As String)
        '插入差分部分
        Dim lastWord As String = ExDocument.GetLastWord()
        If ExColumnMode Then
            If Not ExDocument.FirstColumnLine = 0 Then
                Dim StrText As String = Split(strWord, vbCrLf)(0)
                StrText = Mid(StrText, lastWord.Length + 1)
                ExDocument.InsertString(StrText)
            End If
        Else
            For i As Byte = 1 To lastWord.Length
                ExDocument.DeleteChar(False)
            Next
            Dim strStru As String()
            strStru = Split(strWord, vbCrLf)
            If strStru.Length > 1 Then
                For i As Byte = 1 To strStru.Length - 1
                    strStru(i) = Space(ExDocument.CharPosition - 1) & strStru(i)
                Next
                strWord = ""
                For i As Byte = 0 To strStru.Length - 1
                    strWord &= strStru(i) & vbCrLf
                Next
            End If
            ExDocument.InsertSelection(strWord)
        End If
        RaiseEvent InputPositionChanged()
        Me.Refresh()
    End Sub
    ''' <summary>
    ''' 水平位置变更
    ''' </summary>
    ''' <param name="_offset"></param>
    ''' <remarks></remarks>
    Public Sub SetWidthOffset(ByVal _offset As Integer)
        offset = _offset * offsetUnit
        Me.Refresh()
    End Sub
    ''' <summary>
    ''' 获得光标宽度
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function GetCaretWidth() As Byte
        With ExDocument
            Select Case .GetMemoryLine(.InputPosition.Y).Text.Length
                Case .InputPosition.X - 1
                    Return 1
                Case Else
                    Dim strText = Mid(.GetMemoryLine(.InputPosition.Y).Text, .InputPosition.X, 1)
                    Return MeasureTextWidth(strText)
            End Select
        End With
    End Function
    ''' <summary>
    ''' 检查是否应该出现智能完成
    ''' </summary>
    ''' <remarks></remarks>
    Sub TestAutoComplete()
        '如果不为Space的话，获取当前输入的词语，用来完成补完操作
        If ExDocument.ExRealLineManager.GetInfoLine(ExDocument.GetCurrentDisplayLine.Dis_RealLineNumber).Real_IsCommentLine Then Exit Sub
        Dim lastWord As String = ExDocument.GetLastWord()
        If IntellisenseBox.IsWordInList(lastWord) Then
            If Me.Height - (CaretPosition.Y + ExFont.Height) > IntellisenseBox.Height Then
                IntellisenseBox.Location = New Point(CaretPosition.X - MeasureTextWidth(lastWord), CaretPosition.Y + ExFont.Height)
            Else
                IntellisenseBox.Location = New Point(CaretPosition.X - MeasureTextWidth(lastWord), CaretPosition.Y - IntellisenseBox.Height)
            End If
            IntellisenseBox.Visible = True
            IntellisenseBox.SelectedIndex = -1
        End If
    End Sub
    ''' <summary>
    ''' 根据当前行的字节位置获取新行的光标停留位置的字符位置
    ''' </summary>
    ''' <returns>新行的字符位置,光标在第几个字符之后表示</returns>
    ''' <remarks></remarks>
    Private Function GetUpOrDownPoint(ByVal IsUp As Boolean) As Point
        Dim CurrentWidth As Integer
        Dim strNext As String
        If IsUp Then
            strNext = ExDocument.GetMemoryLineByDisplayLineNumber(ExDocument.DisplayPosition.Y - 1).Text
        Else
            strNext = ExDocument.GetMemoryLineByDisplayLineNumber(ExDocument.DisplayPosition.Y + 1).Text
        End If
        Dim NextXPosition As Integer
        Dim NextYPosition As Integer = IIf(IsUp, ExDocument.DisplayPosition.Y - 1, ExDocument.DisplayPosition.Y + 1)
        '如果目标行是空，或者当前列为行首
        If strNext.Length = 0 Or ExDocument.DisplayPosition.X = 1 Then
            NextXPosition = 1
            Return New Point(NextXPosition, NextYPosition)
        End If
        CurrentWidth = MeasureTextWidth(Mid(ExDocument.CurrentLineText, 1, ExDocument.DisplayPosition.X - 1))
        If CurrentWidth <= MeasureTextWidth(ExDocument.GetMemoryLineByDisplayLineNumber(NextYPosition).Text) Then
            '确定光标位置
            For i As Integer = 1 To strNext.Length
                If MeasureTextWidth(Mid(strNext, 1, i)) >= CurrentWidth Then
                    NextXPosition = i + 1
                    Return New Point(NextXPosition, NextYPosition)
                End If
            Next
        Else
            '如果下一行字符串的字节数小于或者等于这行的光标前字符的字节书，则在最后表示光标
            NextXPosition = ExDocument.GetMemoryLineByDisplayLineNumber(NextYPosition).Text.Length + 1
            Return New Point(NextXPosition, NextYPosition)
        End If
    End Function
#End Region
End Class
